package com.minis.beans.factory.support;

import com.minis.BeansException;
import com.minis.beans.PropertyValue;
import com.minis.beans.PropertyValues;
import com.minis.beans.factory.BeanFactory;
import com.minis.beans.factory.config.ArgumentValue;
import com.minis.beans.factory.config.ArgumentValues;
import com.minis.beans.factory.config.BeanDefinition;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 功能描述
 *
 * @author wanghongqian
 * @date 2023/10/17 17:24
 */
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry
        implements ConfigurableBeanFactory,BeanDefinitionRegistry {

    protected Map<String,BeanDefinition> beanDefinitionMap=new ConcurrentHashMap<>(256);
    protected List<String> beanDefinitionNames=new ArrayList<>(256);
    protected final Map<String,Object> earlySingletonObjects = new HashMap<>(16);

    public AbstractBeanFactory() {

    }

    @Override
    public Object getBean(String beanName) throws BeansException {
        Object singleton = getSingleton(beanName);
        if(singleton == null){
            // 如果没有实例，则尝试从毛胚实例中获取
            singleton = earlySingletonObjects.get(beanName);
            if(singleton == null){
                BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
                if(beanDefinition == null){
                    throw new BeansException("No bean");
                }
                singleton = createBean(beanDefinition);
                registerBean(beanName,singleton);

                // 调用后处理方法
                // step1：postProcessBeforeInitialization
                applyBeanPostProcessorBeforeInitialization(singleton,beanName);
                // step2：调用初始化方法
                if(beanDefinition.getInitMethodName() != null && !beanDefinition.equals("")){
                    invokeInitMethod(beanDefinition,singleton);
                }
                // step3：postProcessAfterInitialization
                applyBeanPostProcessorAfterInitialization(singleton,beanName);

                removeEarlySingleObject(beanName);
            }

        }
        return singleton;
    }

    private void invokeInitMethod(BeanDefinition beanDefinition, Object singleton) {
        Class<?> clz = beanDefinition.getClass();
        Method method = null;
        try {
            method = clz.getMethod(beanDefinition.getInitMethodName());
            method.invoke(singleton);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    private void removeEarlySingleObject(String beanName) {
        earlySingletonObjects.remove(beanName);
    }

    private Object createBean(BeanDefinition beanDefinition){

        Class<?> clazz = null;
        Object obj = doCreateBean(beanDefinition);
        this.earlySingletonObjects.put(beanDefinition.getId(),obj);

        try {
            clazz = Class.forName(beanDefinition.getClassName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        handleProperties(beanDefinition,clazz,obj);
        return obj;
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
        Class<?> clazz = null;
        Object obj = null;
        Constructor<?> constructor = null;

        try {
            clazz = Class.forName(beanDefinition.getClassName());
            ArgumentValues argumentValues = beanDefinition.getConstructorArgumentValues();
            // 构造器参数
            if(!argumentValues.isEmpty()){
                int n = argumentValues.getArgumentCount();
                Class<?>[] paramTypes = new Class<?>[n];
                Object[] paramValues = new Object[n];
                for(int i=0;i<n;i++){
                    ArgumentValue argumentValue = argumentValues.getIndexedArgumentValue(i);
                    String type = argumentValue.getType();
                    Object value = argumentValue.getValue();
                    boolean isRef = argumentValue.isRef();

                    if(!isRef){
                        if("String".equals(type) || "java.lang.String".equals(type)){
                            paramTypes[i] = String.class;
                            paramValues[i] = value;
                        }else if("Integer".equals(type) || "java.lang.Integer".equals(type)){
                            paramTypes[i] = Integer.class;
                            paramValues[i] = Integer.valueOf((String) value) ;
                        }else if("int".equals(type)) {
                            paramTypes[i] = int.class;
                            paramValues[i] = Integer.valueOf((String) value);
                        }else{
                            paramTypes[i] = String.class;
                            paramValues[i] = value;
                        }
                    }else{
                        paramTypes[i] = Class.forName(type);
                        paramValues[i] = getBean((String) value);
                    }
                }
                constructor =  clazz.getConstructor(paramTypes);
                obj = constructor.newInstance(paramValues);
            }else{
                constructor = clazz.getConstructor();
                obj = constructor.newInstance();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (BeansException e) {
            e.printStackTrace();
        }

        return obj;
    }

    // 属性注入
    private void handleProperties(BeanDefinition beanDefinition, Class<?> clazz, Object obj) {
        try{
            PropertyValues propertyValues = beanDefinition.getPropertyValues();
            int n = propertyValues.size();
            if(!propertyValues.isEmpty()){
                for(int i=0;i<n;i++){
                    PropertyValue propertyValue = propertyValues.getPropertyValueList().get(i);
                    String name = propertyValue.getName();
                    String type = propertyValue.getType();
                    Object value = propertyValue.getValue();
                    boolean isRef = propertyValue.isRef();
                    Class<?> paramType;
                    if(!isRef){
                        if("String".equals(type) || "java.lang.String".equals(type)){
                            paramType = String.class;
                        }else if("Integer".equals(type) || "java.lang.Integer".equals(type)){
                            paramType = Integer.class;
                        }else if("int".equals(type)) {
                            paramType = int.class;
                        }else{
                            paramType = String.class;
                        }
                    }else{
                        paramType = Class.forName(type);
                        value = getBean((String) value);
                    }

                    String methodName = "set"+name.substring(0,1).toUpperCase() + name.substring(1);
                    Method method = null;
                    method = clazz.getMethod(methodName,paramType);
                    method.invoke(obj,value);
                }
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (BeansException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Boolean containsBean(String name) {
        return containsSingleton(name);
    }

    @Override
    public boolean isSingleton(String name) {
        return beanDefinitionMap.get(name).isSingleton();
    }

    @Override
    public boolean isPrototype(String name) {
        return beanDefinitionMap.get(name).isPrototype();
    }

    @Override
    public Class<?> getType(String name) {
        return singletons.get(name).getClass();
    }

    @Override
    public void registerBean(String beanName, Object obj) {
        registerSingleton(beanName,obj);
    }

//    public void registerBeanDefinition(BeanDefinition beanDefinition) {
//        this.beanDefinitions.put(beanDefinition.getId(),beanDefinition);
//    }

    @Override
    public void registerBeanDefinition(String name, BeanDefinition bd) {
        this.beanDefinitionMap.put(name,bd);
        this.beanDefinitionNames.add(name);
        if (!bd.isLazyInit()) {
            try {
                getBean(name);
            } catch (BeansException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    @Override
    public void removeBeanDefinition(String name) {
        this.beanDefinitionMap.remove(name);
        this.beanDefinitionNames.remove(name);
        this.removeSingleton(name);

    }

    @Override
    public BeanDefinition getBeanDefinition(String name) {
        return this.beanDefinitionMap.get(name);
    }

    @Override
    public boolean containsBeanDefinition(String name) {
        return this.beanDefinitionMap.containsKey(name);
    }



    public void refresh(){
        for(String beanName:beanNames){
            try {
                getBean(beanName);
            } catch (BeansException e) {
                e.printStackTrace();
            }
        }
    }

    abstract public Object applyBeanPostProcessorBeforeInitialization(Object existingBean,String beanName) throws BeansException;

    abstract public Object applyBeanPostProcessorAfterInitialization(Object existingBean,String beanName) throws BeansException;
}
